/*
 * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2016, 2021 IBM Corporation. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     08/24/2016 - Will Dazey
//       - 500145 : Nested Embeddables Test
//     10/19/2016 - Will Dazey
//       - 506168 : Nested Embeddables AttributeOverride Test
package org.eclipse.persistence.jpa.embeddable;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Query;

import java.util.List;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.jpa.embeddable.model.Address;
import org.eclipse.persistence.jpa.embeddable.model.DeepOrderPK;
import org.eclipse.persistence.jpa.embeddable.model.DeepOrder;
import org.eclipse.persistence.jpa.embeddable.model.Order;
import org.eclipse.persistence.jpa.embeddable.model.OrderPK;
import org.eclipse.persistence.jpa.embeddable.model.SpecAddress;
import org.eclipse.persistence.jpa.embeddable.model.SpecContactInfo;
import org.eclipse.persistence.jpa.embeddable.model.SpecEmployee;
import org.eclipse.persistence.jpa.embeddable.model.SpecPhone;
import org.eclipse.persistence.jpa.embeddable.model.Zipcode;
import org.eclipse.persistence.jpa.test.framework.DDLGen;
import org.eclipse.persistence.jpa.test.framework.Emf;
import org.eclipse.persistence.jpa.test.framework.EmfRunner;
import org.eclipse.persistence.jpa.test.framework.SQLListener;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(EmfRunner.class)
public class TestNestedEmbeddable {
    @Emf(createTables = DDLGen.DROP_CREATE, classes = { DeepOrder.class, DeepOrderPK.class, Order.class, OrderPK.class, Address.class, Zipcode.class })
        private EntityManagerFactory emf;

    @Emf(name = "SpecPersistenceUnit", createTables = DDLGen.DROP_CREATE, 
            classes = { SpecAddress.class, SpecContactInfo.class, SpecEmployee.class, SpecPhone.class })
    private EntityManagerFactory emf2;

    @SQLListener(name = "SpecPersistenceUnit")
    List<String> _sql;

    @Test
    public void persistTest() {
        if (emf == null)
            return;
        EntityManager em = emf.createEntityManager();
        try {
            em.getTransaction().begin();
            Order o = new Order();
            
            String billingZip = "12345";
            String shippingZip ="54321";
            
            OrderPK opk = new OrderPK();
            opk.setBillingAddress(new Address(new Zipcode(billingZip)));
            opk.setShippingAddress(new Address(new Zipcode(shippingZip)));
            
            o.setId(opk);
            
            em.persist(o);
            em.getTransaction().commit();
            em.clear();
            
            Order foundOrder = em.find(Order.class, o.getId());
            
            Assert.assertNotNull(foundOrder);
            
            Object pk = emf.getPersistenceUnitUtil().getIdentifier(foundOrder);
            Assert.assertTrue(pk instanceof OrderPK);
            
            Assert.assertNotNull(((OrderPK)pk).getBillingAddress());
            Assert.assertNotNull(((OrderPK)pk).getShippingAddress());
            
            Assert.assertEquals(billingZip,((OrderPK)pk).getBillingAddress().getZipcode().getZip());
            Assert.assertEquals(shippingZip,((OrderPK)pk).getShippingAddress().getZipcode().getZip());
        } finally {
            if (em.getTransaction().isActive()) {
                em.getTransaction().rollback();
            }
            em.close();
        }
    }

    @Test
    public void testDeeperEmbeddingMappings() {
        if (emf == null)
            return;
        EntityManager em = emf.createEntityManager();
        try {
            ClassDescriptor orderDescriptor = ((JpaEntityManager)em).getServerSession().getDescriptor(Order.class);
            ClassDescriptor orderpkDescriptor = orderDescriptor.getMappingForAttributeName("id").getReferenceDescriptor();
            ClassDescriptor addressDescriptor = orderpkDescriptor.getMappingForAttributeName("billingAddress").getReferenceDescriptor();
            ClassDescriptor zipcodeDescriptor = addressDescriptor.getMappingForAttributeName("zipcode").getReferenceDescriptor();
            Assert.assertEquals("BILL_ZIP", zipcodeDescriptor.getFields().get(0).getName());

            ClassDescriptor deepOrderDescriptor = ((JpaEntityManager)em).getServerSession().getDescriptor(DeepOrder.class);
            ClassDescriptor deepOrderpkDescriptor = deepOrderDescriptor.getMappingForAttributeName("id").getReferenceDescriptor();
            ClassDescriptor orderpkDescriptor2 = deepOrderpkDescriptor.getMappingForAttributeName("orderpk").getReferenceDescriptor();
            ClassDescriptor addressDescriptor2 = orderpkDescriptor2.getMappingForAttributeName("billingAddress").getReferenceDescriptor();
            ClassDescriptor zipcodeDescriptor2 = addressDescriptor2.getMappingForAttributeName("zipcode").getReferenceDescriptor();
            Assert.assertEquals("deepOverride", zipcodeDescriptor2.getFields().get(0).getName());

        } finally {
            if (em.getTransaction().isActive()) {
                em.getTransaction().rollback();
            }
            em.close();
        }
    }

    @Test
    public void JPQLNestedEmbeddableSelectTests() {
        EntityManager em = emf2.createEntityManager();
        try {
            // Test to make sure that moving around the dot notation for nested embeddables is valid and doesn't change the query
            Query queryEmbed = em.createQuery("SELECT c.primaryAddress.city FROM SpecEmployee e JOIN e.contactInfo c WHERE e.contactInfo.primaryAddress.zipcode = ?1");
            queryEmbed.setParameter(1, "95053");
            queryEmbed.getResultList();
            Assert.assertEquals(1, _sql.size());
            Assert.assertEquals("SELECT CITY FROM SPECEMPLOYEE WHERE (ZIPCODE = ?)", _sql.remove(0));

            queryEmbed = em.createQuery("SELECT p.city FROM SpecEmployee e JOIN e.contactInfo.primaryAddress p WHERE e.contactInfo.primaryAddress.zipcode = ?1");
            queryEmbed.setParameter(1, "95054");
            queryEmbed.getResultList();
            Assert.assertEquals(1, _sql.size());
            Assert.assertEquals("SELECT CITY FROM SPECEMPLOYEE WHERE (ZIPCODE = ?)", _sql.remove(0));
        } finally {
            if (em.isOpen()) {
                em.close();
            }
        }
    }

    @Test
    public void JPQLNestedEmbeddableUpdateTest() {
        if (emf == null)
            return;
        EntityManager em = emf.createEntityManager();
        try {
            //Prepare data for a test
            em.getTransaction().begin();
            Order o = new Order();

            String billingZip = "6789";
            String shippingZip ="9876";

            OrderPK opk = new OrderPK();
            opk.setBillingAddress(new Address(new Zipcode(billingZip)));
            opk.setShippingAddress(new Address(new Zipcode(shippingZip)));

            o.setId(opk);

            em.persist(o);
            em.getTransaction().commit();
            em.clear();

            Order foundOrder = em.find(Order.class, o.getId());
            Assert.assertNotNull(foundOrder);

            em.getTransaction().begin();

            String newBillingZip = "1111";
            String newShippingZip ="2222";

            OrderPK opkNew = new OrderPK();
            opkNew.setBillingAddress(new Address(new Zipcode(newBillingZip)));
            opkNew.setShippingAddress(new Address(new Zipcode(newShippingZip)));
            //Tests nested Embedables as JPQL parameter in UPDATE query
            int updatedCount = em.createQuery("UPDATE Order o SET o.id = :newId WHERE o.id = :id")
                    .setParameter("newId", opkNew)
                    .setParameter("id", foundOrder.getId())
                    .executeUpdate();
            Assert.assertEquals(1, updatedCount);
            em.getTransaction().commit();
            em.clear();

            //Tests nested Embedables as JPQL parameter in SELECT query
            Order orderResult = em.createQuery("SELECT o FROM Order o WHERE o.id = :newId", Order.class)
                    .setParameter("newId", opkNew)
                    .getSingleResult();
            Assert.assertNotNull(orderResult);
            Assert.assertEquals(opkNew, orderResult.getId());

        } finally {
            if (em.isOpen()) {
                em.close();
            }
        }
    }
}
